package com.softtanck.ramessageclient.core.engine

import android.os.Handler
import android.os.Looper
import android.os.Message
import android.os.RemoteException
import android.util.Log
import android.util.SparseArray
import com.softtanck.ramessage.IRaMessenger
import com.softtanck.ramessageclient.core.listener.ClientListenerManager
import com.softtanck.ramessageclient.core.listener.RaRemoteMessageListener
import com.softtanck.ramessageclient.core.model.RaClientBindStatus
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import java.util.concurrent.atomic.AtomicInteger

/**
 * @author Softtanck
 * @date 2022/3/12
 * Description: The base handler for client.
 * @param looper the looper for handler
 * @param raClientBindStatus the [RaClientBindStatus]
 */
internal abstract class BaseClientHandler(looper: Looper, private val raClientBindStatus: RaClientBindStatus) : Handler(looper) {

    companion object {
        private const val TAG: String = "BaseClientHandler"
    }

    val innerMessenger: IRaMessenger.Stub by lazy { RaCustomClientMessengerImpl() }

    /**
     * The transaction ID of a message and generated by the client.
     * Also, It is a thread-safe. see [safelyIncrement]
     */
    private val msgTransactionNumber = AtomicInteger(0)

    /**
     * a output messenger for server. client will send message to server by this messenger.
     */
    private val _outputMessengerStateFlow = MutableStateFlow<IRaMessenger?>(null)

    /**
     * The output messenger for server. client will send message to server by this messenger.
     */
    val outputMessengerStateFlow = _outputMessengerStateFlow.asStateFlow()

    /**
     * Remember all callbacks from the client. And WeakReference is used as value.
     * That can be void memory leaks here.
     */
    protected val singleCallbacks: SparseArray<RaRemoteMessageListener> = SparseArray<RaRemoteMessageListener>()

    /**
     * will be called when a message arrived from server.
     * @param msg the message
     * @param isSync is sync message
     * @return null or message from client, if null, the message will be ignored.
     */
    abstract fun onRemoteMessageArrived(msg: Message, isSync: Boolean): Message?

    /**
     * Send a message to server with sync
     * @param message the message
     * @return null or message from server
     */
    protected fun sendSyncMessageToServer(message: Message): Message? = if (raClientBindStatus.bindStatus.value) {
        try {
            outputMessengerStateFlow.value?.sendSync(message.apply {
                arg1 = safelyIncrement()
            })
        } catch (e: RemoteException) {
            Log.e(TAG, "[CLIENT] Failed to send sync msg to server, msg: $message, reason: ${e.message}")
            null
        }
    } else {
        Log.w(TAG, "[CLIENT] You are disconnected with server, Ignore this the message. msg:$message-sync")
        null
    }

    /**
     * Send a message to server with async
     * @param message the message
     * @param raRemoteMessageListener remote callback
     * @return send success or not
     */
    protected fun sendAsyncMessageToServer(message: Message, raRemoteMessageListener: RaRemoteMessageListener? = null): Boolean {
        if (raClientBindStatus.bindStatus.value) {
            return runCatching {
                outputMessengerStateFlow.value?.let { messenger ->
                    val tempTrxId = safelyIncrement()
                    synchronized(singleCallbacks) {
                        singleCallbacks.put(tempTrxId, raRemoteMessageListener)
                    }
                    messenger.send(message.apply { arg1 = tempTrxId })
                    true
                } ?: run {
                    Log.e(TAG, "[CLIENT] Failed to send async msg to server, msg: $message")
                    false
                }
            }.onFailure { exception ->
                Log.e(TAG, "[CLIENT] Failed to send sync msg to server, msg: $message, reason: ${exception.message}")
                safelyNULLCallbackRemoteMessage(raRemoteMessageListener)
            }.getOrDefault(false)
        } else {
            Log.w(TAG, "[CLIENT] You are disconnected with server, Ignore this the message. msg:$message-async")
            safelyNULLCallbackRemoteMessage(raRemoteMessageListener)
            return false
        }
    }

    /**
     * Send a message to server with async without safety check
     * @param message the message
     * @param raRemoteMessageListener remote callback
     * @return send success or not
     */
    protected fun notSafetySendAsyncMessageToServer(message: Message, raRemoteMessageListener: RaRemoteMessageListener? = null): Boolean {
        return runCatching {
            outputMessengerStateFlow.value?.let { messenger ->
                val tempTrxId = safelyIncrement()
                synchronized(singleCallbacks) {
                    singleCallbacks.put(tempTrxId, raRemoteMessageListener)
                }
                messenger.send(message.apply { arg1 = tempTrxId })
                true
            } ?: run {
                Log.e(TAG, "[CLIENT] Failed to send async msg to server, msg: $message")
                false
            }
        }.onFailure { exception ->
            Log.e(TAG, "[CLIENT] Failed to send sync msg to server, msg: $message, reason: ${exception.message}")
            safelyNULLCallbackRemoteMessage(raRemoteMessageListener)
        }.getOrDefault(false)
    }

    /**
     * This method is auto increment the number of transaction.
     * Will be reset to 0 if it more than [Int.MAX_VALUE]
     */
    private fun safelyIncrement(): Int {
        if (msgTransactionNumber.incrementAndGet() >= Int.MAX_VALUE) {
            @Suppress("ControlFlowWithEmptyBody")
            while (!msgTransactionNumber.compareAndSet(msgTransactionNumber.get(), 0));
        }
        return msgTransactionNumber.get()
    }

    private fun safelyNULLCallbackRemoteMessage(raRemoteMessageListener: RaRemoteMessageListener?) {
        runCatching {
            raRemoteMessageListener?.onMessageArrived(message = null)
        }.onFailure { exception ->
            Log.e(TAG, "[CLIENT] Failed to call onMessageArrived, reason: ${exception.message}")
        }
    }

    /**
     * Core messenger for client. AIDL implementation.
     */
    private inner class RaCustomClientMessengerImpl : IRaMessenger.Stub() {
        override fun send(msg: Message) {
            msg.sendingUid = getCallingUid()
            this@BaseClientHandler.sendMessage(msg)
        }

        override fun sendSync(msg: Message): Message {
            msg.sendingUid = getCallingUid()
            return this@BaseClientHandler.onRemoteMessageArrived(msg, true) ?: return Message.obtain(msg)
        }
    }

    /**
     * Set an outbound messengers from outside
     * @param messenger the outBoundMessenger from server
     */
    open fun setOutBoundMessenger(messenger: IRaMessenger?) {
        _outputMessengerStateFlow.value = messenger
    }

    /**
     * clear all callbacks, like: [RaRemoteMessageListener]
     */
    fun clearAllCallbacks() {
        synchronized(singleCallbacks) {
            singleCallbacks.clear()
        }
        // Generally speaking, the broadcast should be removed by the developers themselves. So I have commented out this code.
        // ClientListenerManager.INSTANCE.clearAllRemoteBroadCastMessageCallbacks(raClientBindStatus.componentName)
    }
}